package tcp;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

import tcp.prop.JobConfig;
import tcp.prop.PropertyManager;

public class TCPForwarder {

	private JobConfig jc;
	private CopyOnWriteArrayList<Socket> connectionSockets = new CopyOnWriteArrayList<Socket>();

	public TCPForwarder(JobConfig jc) {
		this.jc = jc;
	}

	public void run() throws UnknownHostException, IOException {

		Thread opener = new Thread(new ConnectionOpener());
		opener.start();
		while (true) {
			Socket clientSocket = null;
			try {
				clientSocket = new Socket(jc.getSourceIp(), jc.getSourcePort());
			} catch (ConnectException e) {
				System.out.println("[WARN] connection failed, retrying.");
				continue;
			}
			InputStream istream = clientSocket.getInputStream();
			System.out.println("[INFO] Source connection opened.");
			byte[] bbb = new byte[1];
			int result = istream.read(bbb);
			int count = 0;
			while (result != -1) {
				ArrayList<Socket> toDelete = new ArrayList<Socket>();
				for (Socket outSock : connectionSockets) {
					try {
						if (outSock != null && outSock.isConnected() && !outSock.isClosed()) {
							OutputStream outStream = outSock.getOutputStream();
							outStream.write(bbb);
							outStream.flush();
							// for printing purpose
							System.out.print(String.format("%02x", bbb[0]));
							count++;
							if (count > 40) {
								System.out.println();
								count = 0;
							}
						}
					} catch (IOException e) {
						if (outSock != null && !outSock.isClosed()) {
							outSock.close();
							System.out.println("[INFO] One of the connection is lost.");
						}
						toDelete.add(outSock);
					}
				}
				for (Socket deletingSocket : toDelete) {
					connectionSockets.remove(deletingSocket);
					System.out.println("[INFO] Disconnecting from " + deletingSocket.getInetAddress() + ":" + deletingSocket.getPort());
					System.out.println("[INFO] Connection Pool=" + connectionSockets.size() + "/" + jc.getConnectionLimit());
				}
				result = istream.read(bbb);

			}
			clientSocket.close();
			System.out.println("[WARN] Source connection closed.");
		}

	}

	public class ConnectionOpener implements Runnable {
		private ServerSocket welcomeSocket;

		@Override
		public void run() {

			try {
				welcomeSocket = new ServerSocket(jc.getWelcomePort());
				while (true) {

					System.out.println("[INFO] Waiting for connection... ");
					Socket newSocket = welcomeSocket.accept();
					if (connectionSockets.size() < jc.getConnectionLimit()) {
						System.out.println("[INFO] Connected to" + newSocket.getInetAddress() + ":" + newSocket.getPort());
						connectionSockets.add(newSocket);
						System.out.println("[INFO] Connection Pool=" + connectionSockets.size() + "/" + jc.getConnectionLimit());
					} else {
						System.out.println("[INFO] Connection rejected: connection pool is full. " + newSocket.getInetAddress() + ":" + newSocket.getPort());
						connectionSockets.remove(newSocket);
						newSocket.close();
					}

				}
			} catch (IOException e) {
				System.out.println("[ERROR] Connection Opener died.");
				e.printStackTrace();
			}
		}
	}

	public static void main(String argv[]) throws Exception {
		JobConfig jobConfig = PropertyManager.loadProperty(new FileInputStream("tcpForward.conf"));
		new TCPForwarder(jobConfig).run();
	}
}
